package com.rainmonth.common.widgets.pla;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;

import com.rainmonth.common.R;

public class PLAMultiColumnListView extends PLAListView {

    @SuppressWarnings("unused")
    private static final String TAG = "MultiColumnListView";

    private static final int DEFAULT_COLUMN_NUMBER = 2;

    private int mColumnNumber = 2;
    private Column[] mColumns = null;
    private Column mFixedColumn = null;    //column for footers & headers.
    private ParcelableSparseIntArray mItems = new ParcelableSparseIntArray();

    private int mColumnPaddingLeft = 0;
    private int mColumnPaddingRight = 0;

    public PLAMultiColumnListView(Context context) {
        super(context);
        init(null);
    }

    public PLAMultiColumnListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public PLAMultiColumnListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    private Rect mFrameRect = new Rect();

    private void init(AttributeSet attrs) {
        getWindowVisibleDisplayFrame(mFrameRect);

        if (attrs == null) {
            mColumnNumber = DEFAULT_COLUMN_NUMBER;    //default column number is 2.
        } else {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PLAMultiColumnListView);

            int landColNumber = a.getInteger(R.styleable.PLAMultiColumnListView_plaLandscapeColumnNumber, -1);
            int defColNumber = a.getInteger(R.styleable.PLAMultiColumnListView_plaColumnNumber, -1);

            if (mFrameRect.width() > mFrameRect.height() && landColNumber != -1) {
                mColumnNumber = landColNumber;
            } else if (defColNumber != -1) {
                mColumnNumber = defColNumber;
            } else {
                mColumnNumber = DEFAULT_COLUMN_NUMBER;
            }

            mColumnPaddingLeft = a.getDimensionPixelSize(R.styleable.PLAMultiColumnListView_plaColumnPaddingLeft, 0);
            mColumnPaddingRight = a.getDimensionPixelSize(R.styleable.PLAMultiColumnListView_plaColumnPaddingRight, 0);
            a.recycle();
        }

        mColumns = new Column[mColumnNumber];
        for (int i = 0; i < mColumnNumber; ++i)
            mColumns[i] = new Column(i);

        mFixedColumn = new FixedColumn();
    }

    public void setColumnPaddingLeft(int columnPaddingLeft) {
        this.mColumnPaddingLeft = columnPaddingLeft;
    }

    public void setColumnPaddingRight(int columnPaddingRight) {
        this.mColumnPaddingRight = columnPaddingRight;
    }

    ///////////////////////////////////////////////////////////////////////
    //Override Methods...
    ///////////////////////////////////////////////////////////////////////

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //TODO the adapter status may be changed. what should i do here...
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = (getMeasuredWidth() - mListPadding.left - mListPadding.right - mColumnPaddingLeft - mColumnPaddingRight) / mColumnNumber;

        for (int index = 0; index < mColumnNumber; ++index) {
            mColumns[index].mColumnWidth = width;
            mColumns[index].mColumnLeft = mListPadding.left + mColumnPaddingLeft + width * index;
        }

        mFixedColumn.mColumnLeft = mListPadding.left;
        mFixedColumn.mColumnWidth = getMeasuredWidth();
    }

    @Override
    protected void onMeasureChild(View child, int position, int widthMeasureSpec, int heightMeasureSpec) {
        if (isFixedView(child))
            child.measure(widthMeasureSpec, heightMeasureSpec);
        else
            child.measure(MeasureSpec.EXACTLY | getColumnWidth(position), heightMeasureSpec);
    }

    @Override
    protected int modifyFlingInitialVelocity(int initialVelocity) {
        return initialVelocity;
    }

    @Override
    protected void onItemAddedToList(int position, boolean flow) {
        super.onItemAddedToList(position, flow);

        if (isHeaderOrFooterPosition(position) == false) {
            Column col = getNextColumn(flow, position);
            mItems.append(position, col.getIndex());
        }
    }

    @Override
    protected void onLayoutSync(int syncPos) {
        for (Column c : mColumns) {
            c.save();
        }
    }

    @Override
    protected void onLayoutSyncFinished(int syncPos) {
        for (Column c : mColumns) {
            c.clear();
        }
    }

    @Override
    protected void onAdjustChildViews(boolean down) {

        int firstItem = getFirstVisiblePosition();
        if (!down && firstItem == 0) {
            final int firstColumnTop = mColumns[0].getTop();
            for (Column c : mColumns) {
                final int top = c.getTop();
                //align all column's top to 0's column.
                c.offsetTopAndBottom(firstColumnTop - top);
            }
        }
        super.onAdjustChildViews(down);
    }

    @Override
    protected int getFillChildBottom() {
        //return smallest bottom value.
        //in order to determine fill down or not... (calculate below space)
        int result = Integer.MAX_VALUE;
        for (Column c : mColumns) {
            int bottom = c.getBottom();
            result = result > bottom ? bottom : result;
        }
        return result;
    }

    @Override
    protected int getFillChildTop() {
        //find largest column.
        int result = Integer.MIN_VALUE;
        for (Column c : mColumns) {
            int top = c.getTop();
            result = Math.max(result, top);
        }
        return result;
    }

    @Override
    protected int getScrollChildBottom() {
        //return largest bottom value.
        //for checking scrolling region...
        int result = Integer.MIN_VALUE;
        for (Column c : mColumns) {
            int bottom = c.getBottom();
            result = result < bottom ? bottom : result;
        }
        return result;
    }

    @Override
    protected int getScrollChildTop() {
        //find largest column.
        int result = Integer.MAX_VALUE;
        for (Column c : mColumns) {
            int top = c.getTop();
            result = result > top ? top : result;
        }
        return result;
    }

    @Override
    protected int getItemLeft(int pos) {

        if (isHeaderOrFooterPosition(pos))
            return mFixedColumn.getColumnLeft();

        return getColumnLeft(pos);
    }

    @Override
    protected int getItemTop(int pos) {

        if (isHeaderOrFooterPosition(pos))
            return mFixedColumn.getBottom();    //footer view should be placed below the last column.

        int colIndex = mItems.get(pos, -1);
        if (colIndex == -1)
            return getFillChildBottom();

        return mColumns[colIndex].getBottom();
    }

    @Override
    protected int getItemBottom(int pos) {

        if (isHeaderOrFooterPosition(pos))
            return mFixedColumn.getTop();    //header view should be place above the first column item.

        int colIndex = mItems.get(pos, -1);
        if (colIndex == -1)
            return getFillChildTop();

        return mColumns[colIndex].getTop();
    }

    //////////////////////////////////////////////////////////////////////////////
    //Private Methods...
    //////////////////////////////////////////////////////////////////////////////

    //flow If flow is true, align top edge to y. If false, align bottom edge to y.
    private Column getNextColumn(boolean flow, int position) {

        //we already have this item...
        int colIndex = mItems.get(position, -1);
        if (colIndex != -1) {
            return mColumns[colIndex];
        }

        //adjust position (exclude headers...)
        position = Math.max(0, position - getHeaderViewsCount());

        final int lastVisiblePos = Math.max(0, position);
        if (lastVisiblePos < mColumnNumber)
            return mColumns[lastVisiblePos];

        if (flow) {
            //find column which has the smallest bottom value.
            return gettBottomColumn();
        } else {
            //find column which has the smallest top value.
            return getTopColumn();
        }
    }

    private boolean isHeaderOrFooterPosition(int pos) {
        int type = mAdapter.getItemViewType(pos);
        return type == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
    }

    private Column getTopColumn() {
        Column result = mColumns[0];
        for (Column c : mColumns) {
            result = result.getTop() > c.getTop() ? c : result;
        }
        return result;
    }

    private Column gettBottomColumn() {
        Column result = mColumns[0];
        for (Column c : mColumns) {
            result = result.getBottom() > c.getBottom() ? c : result;
        }

        return result;
    }

    private int getColumnLeft(int pos) {
        int colIndex = mItems.get(pos, -1);

        if (colIndex == -1)
            return 0;

        return mColumns[colIndex].getColumnLeft();
    }

    private int getColumnWidth(int pos) {
        int colIndex = mItems.get(pos, -1);

        if (colIndex == -1)
            return 0;

        return mColumns[colIndex].getColumnWidth();
    }

    ///////////////////////////////////////////////////////////////
    //Inner Class.
    ///////////////////////////////////////////////////////////////

    private class Column {

        private int mIndex;
        private int mColumnWidth;
        private int mColumnLeft;
        private int mSynchedTop = 0;
        private int mSynchedBottom = 0;

        //TODO is it ok to use item position info to identify item??

        public Column(int index) {
            mIndex = index;
        }

        public int getColumnLeft() {
            return mColumnLeft;
        }

        public int getColumnWidth() {
            return mColumnWidth;
        }

        public int getIndex() {
            return mIndex;
        }

        public int getBottom() {
            //find biggest value.
            int bottom = Integer.MIN_VALUE;
            int childCount = getChildCount();

            for (int index = 0; index < childCount; ++index) {
                View v = getChildAt(index);

                if (v.getLeft() != mColumnLeft && isFixedView(v) == false)
                    continue;
                bottom = bottom < v.getBottom() ? v.getBottom() : bottom;
            }

            if (bottom == Integer.MIN_VALUE)
                return mSynchedBottom;    //no child for this column..
            return bottom;
        }

        public void offsetTopAndBottom(int offset) {
            if (offset == 0)
                return;

            //find biggest value.
            int childCount = getChildCount();

            for (int index = 0; index < childCount; ++index) {
                View v = getChildAt(index);

                if (v.getLeft() != mColumnLeft && isFixedView(v) == false)
                    continue;

                v.offsetTopAndBottom(offset);
            }
        }

        public int getTop() {
            //find smallest value.
            int top = Integer.MAX_VALUE;
            int childCount = getChildCount();
            for (int index = 0; index < childCount; ++index) {
                View v = getChildAt(index);
                if (v.getLeft() != mColumnLeft && !isFixedView(v))
                    continue;
                top = Math.min(top, v.getTop());
            }

            if (top == Integer.MAX_VALUE)
                return mSynchedTop;    //no child for this column. just return saved sync top..
            return top;
        }

        public void save() {
            mSynchedTop = 0;
            mSynchedBottom = getTop();
        }

        public void clear() {
            mSynchedTop = 0;
            mSynchedBottom = 0;
        }
    }//end of inner class Column

    private class FixedColumn extends Column {

        public FixedColumn() {
            super(Integer.MAX_VALUE);
        }

        @Override
        public int getBottom() {
            return getScrollChildBottom();
        }

        @Override
        public int getTop() {
            return getScrollChildTop();
        }

    }


    private boolean loadingMoreComplete = true;

    public void onLoadMoreComplete() {
        loadingMoreComplete = true;
    }

    public interface OnLoadMoreListener {
        /**
         * Method to be called when scroll to buttom is requested
         */
        void onLoadMore();
    }

    public OnScrollListener scroller = new OnScrollListener() {
        private int visibleLastIndex = 0;
        private static final int OFFSET = 2;

        @Override
        public void onScrollStateChanged(PLAAbsListView view, int scrollState) {
            int lastIndex = getAdapter().getCount() - OFFSET;
            if (scrollState == SCROLL_STATE_IDLE
                    && visibleLastIndex == lastIndex && loadingMoreComplete) {

                loadMoreListener.onLoadMore();
                loadingMoreComplete = false;

            }
        }

        @Override
        public void onScroll(PLAAbsListView view, int firstVisibleItem,
                             int visibleItemCount, int totalItemCount) {
            visibleLastIndex = firstVisibleItem + visibleItemCount - OFFSET;
        }
    };
    OnLoadMoreListener loadMoreListener;

    public void setOnLoadMoreListener(OnLoadMoreListener listener) {

        if (listener != null) {
            this.loadMoreListener = listener;
            this.setOnScrollListener(scroller);
        }
    }//end of class


    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        bundle.putParcelable("items", mItems);
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            mItems = bundle.getParcelable("items");
            state = bundle.getParcelable("instanceState");
        }
        super.onRestoreInstanceState(state);
    }
}//end of class
